<html lang="en-US">

<head>
    
<title>从实践到原理，带你参透 gRPC - 花落雨忧</title>

<meta property="og:title" content="从实践到原理，带你参透 gRPC - 花落雨忧">



    



    
    <meta property="description" content="
gRPC 在 Go 语言中大放异彩，越来越多的小伙伴在使用，最近也在公司安利了一波，希望这一篇文章能带你一览 gRPC 的巧妙之处，本文篇幅比较长，请做好阅读准备。本文目录如下：
[&amp;hellip;] gRPC 是一个高性能、开源和通用的 RPC 框架，面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本，分别是：grpc, grpc-java, grpc-go. 其中 C &amp;hellip;">
    <meta property="og:description" content="
gRPC 在 Go 语言中大放异彩，越来越多的小伙伴在使用，最近也在公司安利了一波，希望这一篇文章能带你一览 gRPC 的巧妙之处，本文篇幅比较长，请做好阅读准备。本文目录如下：
[&amp;hellip;] gRPC 是一个高性能、开源和通用的 RPC 框架，面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本，分别是：grpc, grpc-java, grpc-go. 其中 C &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.asia/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.asia/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.asia/js/jquery.min.js"></script>




<link href="https://www.lican.asia/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>

        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/posts/go/talk/2019-06-29-talking-grpc/">从实践到原理，带你参透 gRPC</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2019/06/29</a>
                                            
                                            
                                            
                                            By 
                                            <a class="author" href="javascript:;">煎鱼</a></div>
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p><img src="https://image.eddycjy.com/4a47a0db6e60853dedfcfdf08a5ca249.png" alt="image"></p>
<p>gRPC 在 Go 语言中大放异彩，越来越多的小伙伴在使用，最近也在公司安利了一波，希望这一篇文章能带你一览 gRPC 的巧妙之处，本文篇幅比较长，请做好阅读准备。本文目录如下：</p>
<p><img src="https://image.eddycjy.com/156005c5baf40ff51a327f1c34f2975b.jpg" alt="image"></p>
<h2 id="简述">简述</h2>
<p>gRPC 是一个高性能、开源和通用的 RPC 框架，面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本，分别是：grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。</p>
<p>gRPC 基于 HTTP/2 标准设计，带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好，更省电和节省空间占用。</p>
<h2 id="调用模型">调用模型</h2>
<p><img src="https://image.eddycjy.com/10fb15c77258a991b0028080a64fb42d.png" alt="image"></p>
<p>1、客户端（gRPC Stub）调用 A 方法，发起 RPC 调用。</p>
<p>2、对请求信息使用 Protobuf 进行对象序列化压缩（IDL）。</p>
<p>3、服务端（gRPC Server）接收到请求后，解码请求体，进行业务逻辑处理并返回。</p>
<p>4、对响应结果使用 Protobuf 进行对象序列化压缩（IDL）。</p>
<p>5、客户端接受到服务端响应，解码请求体。回调被调用的 A 方法，唤醒正在等待响应（阻塞）的客户端调用并返回响应结果。</p>
<h2 id="调用方式">调用方式</h2>
<h3 id="一unary-rpc一元-rpc">一、Unary RPC：一元 RPC</h3>
<p><img src="https://image.eddycjy.com/09dd8c2662b96ce14928333f055c5580.png" alt="image"></p>
<h4 id="server">Server</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SearchService</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">SearchService</span>) <span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">SearchRequest</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">SearchResponse</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">SearchResponse</span>{<span style="color:#a6e22e">Response</span>: <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GetRequest</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; Server&#34;</span>}, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">PORT</span> = <span style="color:#e6db74">&#34;9001&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">server</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">NewServer</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">RegisterSearchServiceServer</span>(<span style="color:#a6e22e">server</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">SearchService</span>{})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">lis</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">Listen</span>(<span style="color:#e6db74">&#34;tcp&#34;</span>, <span style="color:#e6db74">&#34;:&#34;</span><span style="color:#f92672">+</span><span style="color:#a6e22e">PORT</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">Serve</span>(<span style="color:#a6e22e">lis</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li>创建 gRPC Server 对象，你可以理解为它是 Server 端的抽象对象。</li>
<li>将 SearchService（其包含需要被调用的服务端接口）注册到 gRPC Server。 的内部注册中心。这样可以在接受到请求时，通过内部的 “服务发现”，发现该服务端接口并转接进行逻辑处理。</li>
<li>创建 Listen，监听 TCP 端口。</li>
<li>gRPC Server 开始 lis.Accept，直到 Stop 或 GracefulStop。</li>
</ul>
<h4 id="client">Client</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">Dial</span>(<span style="color:#e6db74">&#34;:&#34;</span><span style="color:#f92672">+</span><span style="color:#a6e22e">PORT</span>, <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithInsecure</span>())
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">NewSearchServiceClient</span>(<span style="color:#a6e22e">conn</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">SearchRequest</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Request</span>: <span style="color:#e6db74">&#34;gRPC&#34;</span>,
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li>创建与给定目标（服务端）的连接句柄。</li>
<li>创建 SearchService 的客户端对象。</li>
<li>发送 RPC 请求，等待同步响应，得到回调后返回响应结果。</li>
</ul>
<h3 id="二server-side-streaming-rpc服务端流式-rpc">二、Server-side streaming RPC：服务端流式 RPC</h3>
<p><img src="https://image.eddycjy.com/8266e4bfeda1bd42d8f9794eb4ea0a13.png" alt="image"></p>
<h4 id="server-1">Server</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">StreamService</span>) <span style="color:#a6e22e">List</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamRequest</span>, <span style="color:#a6e22e">stream</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamService_ListServer</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">n</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">6</span>; <span style="color:#a6e22e">n</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Send</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamResponse</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Pt</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamPoint</span>{
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="client-1">Client</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">printLists</span>(<span style="color:#a6e22e">client</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamServiceClient</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamRequest</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stream</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">List</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Recv</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="三client-side-streaming-rpc客户端流式-rpc">三、Client-side streaming RPC：客户端流式 RPC</h3>
<p><img src="https://image.eddycjy.com/f19c9085129709ee14d013be869df69b.png" alt="image"></p>
<h4 id="server-2">Server</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">StreamService</span>) <span style="color:#a6e22e">Record</span>(<span style="color:#a6e22e">stream</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamService_RecordServer</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">r</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Recv</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">SendAndClose</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamResponse</span>{<span style="color:#a6e22e">Pt</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamPoint</span>{<span style="color:#f92672">...</span>}})
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="client-2">Client</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">printRecord</span>(<span style="color:#a6e22e">client</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamServiceClient</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamRequest</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stream</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Record</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>())
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">n</span> &lt; <span style="color:#ae81ff">6</span>; <span style="color:#a6e22e">n</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Send</span>(<span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">CloseAndRecv</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="四bidirectional-streaming-rpc双向流式-rpc">四、Bidirectional streaming RPC：双向流式 RPC</h3>
<p><img src="https://image.eddycjy.com/9eb9cd58b9ea5e04c890326b5c1f471f.png" alt="image"></p>
<h4 id="server-3">Server</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">StreamService</span>) <span style="color:#a6e22e">Route</span>(<span style="color:#a6e22e">stream</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamService_RouteServer</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Send</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamResponse</span>{<span style="color:#f92672">...</span>})
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">r</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Recv</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="client-3">Client</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">printRoute</span>(<span style="color:#a6e22e">client</span> <span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamServiceClient</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">StreamRequest</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stream</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Route</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>())
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">n</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">6</span>; <span style="color:#a6e22e">n</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Send</span>(<span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">Recv</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">CloseSend</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="客户端与服务端是如何交互的">客户端与服务端是如何交互的</h2>
<p>在开始分析之前，我们要先 gRPC 的调用有一个初始印象。那么最简单的就是对 Client 端调用 Server 端进行抓包去剖析，看看整个过程中它都做了些什么事。如下图：</p>
<p><img src="https://image.eddycjy.com/8cda81fc7ad906927144235dda5fdf15.jpg" alt="image"></p>
<ul>
<li>Magic</li>
<li>SETTINGS</li>
<li>HEADERS</li>
<li>DATA</li>
<li>SETTINGS</li>
<li>WINDOW_UPDATE</li>
<li>PING</li>
<li>HEADERS</li>
<li>DATA</li>
<li>HEADERS</li>
<li>WINDOW_UPDATE</li>
<li>PING</li>
</ul>
<p>我们略加整理发现共有十二个行为，是比较重要的。在开始分析之前，建议你自己先想一下，它们的作用都是什么？大胆猜测一下，带着疑问去学习效果更佳。</p>
<h3 id="行为分析">行为分析</h3>
<h4 id="magic">Magic</h4>
<p><img src="https://image.eddycjy.com/30e62fddc14c05988b44e7c02788e187.jpg" alt="image"></p>
<p>Magic 帧的主要作用是建立 HTTP/2 请求的前言。在 HTTP/2 中，要求两端都要发送一个连接前言，作为对所使用协议的最终确认，并确定 HTTP/2 连接的初始设置，客户端和服务端各自发送不同的连接前言。</p>
<p>而上图中的 Magic 帧是客户端的前言之一，内容为 <code>PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n</code>，以确定启用 HTTP/2 连接。</p>
<h4 id="settings">SETTINGS</h4>
<p><img src="https://image.eddycjy.com/ae566253288191ce5d879e51dae1d8c3.jpg" alt="image"></p>
<p><img src="https://image.eddycjy.com/62bf1edb36141f114521ec4bb4175579.jpg" alt="image"></p>
<p>SETTINGS 帧的主要作用是设置这一个连接的参数，作用域是整个连接而并非单一的流。</p>
<p>而上图的 SETTINGS 帧都是空 SETTINGS 帧，图一是客户端连接的前言（Magic 和 SETTINGS 帧分别组成连接前言）。图二是服务端的。另外我们从图中可以看到多个 SETTINGS 帧，这是为什么呢？是因为发送完连接前言后，客户端和服务端还需要有一步互动确认的动作。对应的就是带有 ACK 标识 SETTINGS 帧。</p>
<h4 id="headers">HEADERS</h4>
<p><img src="https://image.eddycjy.com/8df7b73a7820f4aef47864f2a6c5fccf.jpg" alt="image"></p>
<p>HEADERS 帧的主要作用是存储和传播 HTTP 的标头信息。我们关注到 HEADERS 里有一些眼熟的信息，分别如下：</p>
<ul>
<li>method：POST</li>
<li>scheme：http</li>
<li>path：/proto.SearchService/Search</li>
<li>authority：:10001</li>
<li>content-type：application/grpc</li>
<li>user-agent：grpc-go/1.20.0-dev</li>
</ul>
<p>你会发现这些东西非常眼熟，其实都是 gRPC 的基础属性，实际上远远不止这些，只是设置了多少展示多少。例如像平时常见的 <code>grpc-timeout</code>、<code>grpc-encoding</code> 也是在这里设置的。</p>
<h4 id="data">DATA</h4>
<p><img src="https://image.eddycjy.com/9414a8f5b810972c3c9a0e2860c07532.jpg" alt="image"></p>
<p>DATA 帧的主要作用是装填主体信息，是数据帧。而在上图中，可以很明显看到我们的请求参数 gRPC 存储在里面。只需要了解到这一点就可以了。</p>
<h4 id="headers-data-headers">HEADERS, DATA, HEADERS</h4>
<p><img src="https://image.eddycjy.com/edab7ba7e203cd7576d1200465194ea8.jpg" alt="image"></p>
<p>在上图中 HEADERS 帧比较简单，就是告诉我们 HTTP 响应状态和响应的内容格式。</p>
<p><img src="https://image.eddycjy.com/db3a17f7bcac837ecc1fe2bc630a5473.jpg" alt="imgae"></p>
<p>在上图中 DATA 帧主要承载了响应结果的数据集，图中的 gRPC Server 就是我们 RPC 方法的响应结果。</p>
<p><img src="https://image.eddycjy.com/85b6f89b41cae26786ac72365fff771b.jpg" alt="image"></p>
<p>在上图中 HEADERS 帧主要承载了 gRPC 状态 和 gRPC 状态消息，图中的 <code>grpc-status</code> 和 <code>grpc-message</code> 就是我们的 gRPC 调用状态的结果。</p>
<h3 id="其它步骤">其它步骤</h3>
<h4 id="window_update">WINDOW_UPDATE</h4>
<p>主要作用是管理和流的窗口控制。通常情况下打开一个连接后，服务器和客户端会立即交换 SETTINGS 帧来确定流控制窗口的大小。默认情况下，该大小设置为约 65 KB，但可通过发出一个 WINDOW_UPDATE 帧为流控制设置不同的大小。</p>
<p><img src="https://image.eddycjy.com/a269962fe1424e1ca3e68c328b9fed61.jpg" alt="image"></p>
<h4 id="pingpong">PING/PONG</h4>
<p>主要作用是判断当前连接是否仍然可用，也常用于计算往返时间。其实也就是 PING/PONG，大家对此应该很熟。</p>
<h3 id="小结">小结</h3>
<p><img src="https://image.eddycjy.com/ba6beb7ae28ef0a97d7a0a038feb5060.png" alt="image"></p>
<ul>
<li>在建立连接之前，客户端/服务端都会发送<strong>连接前言</strong>（Magic+SETTINGS），确立协议和配置项。</li>
<li>在传输数据时，是会涉及滑动窗口（WINDOW_UPDATE）等流控策略的。</li>
<li>传播 gRPC 附加信息时，是基于 HEADERS 帧进行传播和设置；而具体的请求/响应数据是存储的 DATA 帧中的。</li>
<li>请求/响应结果会分为 HTTP 和 gRPC 状态响应两种类型。</li>
<li>客户端发起 PING，服务端就会回应 PONG，反之亦可。</li>
</ul>
<p>这块 gRPC 的基础使用，你可以看看我另外的 <a href="https://github.com/EDDYCJY/blog#grpc%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">《gRPC 入门系列》</a>，相信对你一定有帮助。</p>
<h2 id="浅谈理解">浅谈理解</h2>
<h3 id="服务端">服务端</h3>
<p><img src="https://image.eddycjy.com/7134f8f5aced525d1c11d229063305e7.png" alt="image"></p>
<p>为什么四行代码，就能够起一个 gRPC Server，内部做了什么逻辑。你有想过吗？接下来我们一步步剖析，看看里面到底是何方神圣。</p>
<h3 id="一初始化">一、初始化</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// grpc.NewServer()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewServer</span>(<span style="color:#a6e22e">opt</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">ServerOption</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">Server</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">opts</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">defaultServerOptions</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">o</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">opt</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">o</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">opts</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">lis</span>:    make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">Listener</span>]<span style="color:#66d9ef">bool</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">opts</span>:   <span style="color:#a6e22e">opts</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">conns</span>:  make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">Closer</span>]<span style="color:#66d9ef">bool</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">m</span>:      make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">service</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">quit</span>:   make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{}),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">done</span>:   make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">struct</span>{}),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">czData</span>: new(<span style="color:#a6e22e">channelzData</span>),
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">cv</span> = <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">NewCond</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">mu</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>这块比较简单，主要是实例 grpc.Server 并进行初始化动作。涉及如下：</p>
<ul>
<li>lis：监听地址列表。</li>
<li>opts：服务选项，这块包含 Credentials、Interceptor 以及一些基础配置。</li>
<li>conns：客户端连接句柄列表。</li>
<li>m：服务信息映射。</li>
<li>quit：退出信号。</li>
<li>done：完成信号。</li>
<li>czData：用于存储 ClientConn，addrConn 和 Server 的 channelz 相关数据。</li>
<li>cv：当优雅退出时，会等待这个信号量，直到所有 RPC 请求都处理并断开才会继续处理。</li>
</ul>
<h3 id="二注册">二、注册</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">pb</span>.<span style="color:#a6e22e">RegisterSearchServiceServer</span>(<span style="color:#a6e22e">server</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">SearchService</span>{})
</span></span></code></pre></div><h4 id="步骤一service-api-interface">步骤一：Service API interface</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// search.pb.go
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SearchServiceServer</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">SearchRequest</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">SearchResponse</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">RegisterSearchServiceServer</span>(<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">Server</span>, <span style="color:#a6e22e">srv</span> <span style="color:#a6e22e">SearchServiceServer</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">RegisterService</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">_SearchService_serviceDesc</span>, <span style="color:#a6e22e">srv</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>还记得我们平时编写的 Protobuf 吗？在生成出来的 <code>.pb.go</code> 文件中，会定义出 Service APIs interface 的具体实现约束。而我们在 gRPC Server 进行注册时，会传入应用 Service 的功能接口实现，此时生成的 <code>RegisterServer</code> 方法就会保证两者之间的一致性。</p>
<h4 id="步骤二service-api-idl">步骤二：Service API IDL</h4>
<p>你想乱传糊弄一下？不可能的，请乖乖定义与 Protobuf 一致的接口方法。但是那个 <code>&amp;_SearchService_serviceDesc</code> 又有什么作用呢？代码如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// search.pb.go
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">_SearchService_serviceDesc</span> = <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ServiceDesc</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ServiceName</span>: <span style="color:#e6db74">&#34;proto.SearchService&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HandlerType</span>: (<span style="color:#f92672">*</span><span style="color:#a6e22e">SearchServiceServer</span>)(<span style="color:#66d9ef">nil</span>),
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Methods</span>: []<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">MethodDesc</span>{
</span></span><span style="display:flex;"><span>		{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">MethodName</span>: <span style="color:#e6db74">&#34;Search&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">Handler</span>:    <span style="color:#a6e22e">_SearchService_Search_Handler</span>,
</span></span><span style="display:flex;"><span>		},
</span></span><span style="display:flex;"><span>	},
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Streams</span>:  []<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">StreamDesc</span>{},
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Metadata</span>: <span style="color:#e6db74">&#34;search.proto&#34;</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>这看上去像服务的描述代码，用来向内部表述 “我” 都有什么。涉及如下:</p>
<ul>
<li>ServiceName：服务名称</li>
<li>HandlerType：服务接口，用于检查用户提供的实现是否满足接口要求</li>
<li>Methods：一元方法集，注意结构内的 <code>Handler</code> 方法，其对应最终的 RPC 处理方法，在执行 RPC 方法的阶段会使用。</li>
<li>Streams：流式方法集</li>
<li>Metadata：元数据，是一个描述数据属性的东西。在这里主要是描述 <code>SearchServiceServer</code> 服务</li>
</ul>
<h4 id="步骤三register-service">步骤三：Register Service</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Server</span>) <span style="color:#a6e22e">register</span>(<span style="color:#a6e22e">sd</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ServiceDesc</span>, <span style="color:#a6e22e">ss</span> <span style="color:#66d9ef">interface</span>{}) {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">service</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">server</span>: <span style="color:#a6e22e">ss</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">md</span>:     make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">MethodDesc</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">sd</span>:     make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">StreamDesc</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mdata</span>:  <span style="color:#a6e22e">sd</span>.<span style="color:#a6e22e">Metadata</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">sd</span>.<span style="color:#a6e22e">Methods</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">d</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sd</span>.<span style="color:#a6e22e">Methods</span>[<span style="color:#a6e22e">i</span>]
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">md</span>[<span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">MethodName</span>] = <span style="color:#a6e22e">d</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">sd</span>.<span style="color:#a6e22e">Streams</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">m</span>[<span style="color:#a6e22e">sd</span>.<span style="color:#a6e22e">ServiceName</span>] = <span style="color:#a6e22e">srv</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>在最后一步中，我们会将先前的服务接口信息、服务描述信息给注册到内部 <code>service</code> 去，以便于后续实际调用的使用。涉及如下：</p>
<ul>
<li>server：服务的接口信息</li>
<li>md：一元服务的 RPC 方法集</li>
<li>sd：流式服务的 RPC 方法集</li>
<li>mdata：metadata，元数据</li>
</ul>
<h4 id="小结-1">小结</h4>
<p>在这一章节中，主要介绍的是 gRPC Server 在启动前的整理和注册行为，看上去很简单，但其实一切都是为了后续的实际运行的预先准备。因此我们整理一下思路，将其串联起来看看，如下：</p>
<p><img src="https://image.eddycjy.com/75c168b671d4ce827fca23907d85f114.png" alt="image"></p>
<h3 id="三监听">三、监听</h3>
<p>接下来到了整个流程中，最重要也是大家最关注的监听/处理阶段，核心代码如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Server</span>) <span style="color:#a6e22e">Serve</span>(<span style="color:#a6e22e">lis</span> <span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">Listener</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">tempDelay</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rawConn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">lis</span>.<span style="color:#a6e22e">Accept</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ne</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">err</span>.(<span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">Temporary</span>() <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>			}); <span style="color:#a6e22e">ok</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">ne</span>.<span style="color:#a6e22e">Temporary</span>() {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">tempDelay</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">tempDelay</span> = <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>
</span></span><span style="display:flex;"><span>				} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">tempDelay</span> <span style="color:#f92672">*=</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">max</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>; <span style="color:#a6e22e">tempDelay</span> &gt; <span style="color:#a6e22e">max</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">tempDelay</span> = <span style="color:#a6e22e">max</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">timer</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">NewTimer</span>(<span style="color:#a6e22e">tempDelay</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">timer</span>.<span style="color:#a6e22e">C</span>:
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">quit</span>:
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">timer</span>.<span style="color:#a6e22e">Stop</span>()
</span></span><span style="display:flex;"><span>					<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">tempDelay</span> = <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">serveWG</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">handleRawConn</span>(<span style="color:#a6e22e">rawConn</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">serveWG</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>		}()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Serve 会根据外部传入的 Listener 不同而调用不同的监听模式，这也是 <code>net.Listener</code> 的魅力，灵活性和扩展性会比较高。而在 gRPC Server 中最常用的就是 <code>TCPConn</code>，基于 TCP Listener 去做。接下来我们一起看看具体的处理逻辑，如下：</p>
<p><img src="https://image.eddycjy.com/7ae5e99a8c2f19cd25f44313293553aa.png" alt="image"></p>
<ul>
<li>循环处理连接，通过 <code>lis.Accept</code> 取出连接，如果队列中没有需处理的连接时，会形成阻塞等待。</li>
<li>若 <code>lis.Accept</code> 失败，则触发休眠机制，若为第一次失败那么休眠 5ms，否则翻倍，再次失败则不断翻倍直至上限休眠时间 1s，而休眠完毕后就会尝试去取下一个 “它”。</li>
<li>若 <code>lis.Accept</code> 成功，则重置休眠的时间计数和启动一个新的 goroutine 调用 <code>handleRawConn</code> 方法去执行/处理新的请求，也就是大家很喜欢说的 “每一个请求都是不同的 goroutine 在处理”。</li>
<li>在循环过程中，包含了 “退出” 服务的场景，主要是硬关闭和优雅重启服务两种情况。</li>
</ul>
<h2 id="客户端">客户端</h2>
<p><img src="https://image.eddycjy.com/2484a7df36877a14689574eebda6dd7c.png" alt="image"></p>
<h3 id="一创建拨号连接">一、创建拨号连接</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// grpc.Dial(&#34;:&#34;+PORT, grpc.WithInsecure())
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">DialContext</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">target</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">DialOption</span>) (<span style="color:#a6e22e">conn</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cc</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ClientConn</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">target</span>:            <span style="color:#a6e22e">target</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">csMgr</span>:             <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">connectivityStateManager</span>{},
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">conns</span>:             make(<span style="color:#66d9ef">map</span>[<span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>]<span style="color:#66d9ef">struct</span>{}),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">dopts</span>:             <span style="color:#a6e22e">defaultDialOptions</span>(),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">blockingpicker</span>:    <span style="color:#a6e22e">newPickerWrapper</span>(),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">czData</span>:            new(<span style="color:#a6e22e">channelzData</span>),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">firstResolveEvent</span>: <span style="color:#a6e22e">grpcsync</span>.<span style="color:#a6e22e">NewEvent</span>(),
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">chainUnaryClientInterceptors</span>(<span style="color:#a6e22e">cc</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">chainStreamClientInterceptors</span>(<span style="color:#a6e22e">cc</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>grpc.Dial</code> 方法实际上是对于 <code>grpc.DialContext</code> 的封装，区别在于 <code>ctx</code> 是直接传入 <code>context.Background</code>。其主要功能是<strong>创建</strong>与给定目标的客户端连接，其承担了以下职责：</p>
<ul>
<li>初始化 ClientConn</li>
<li>初始化（基于进程 LB）负载均衡配置</li>
<li>初始化 channelz</li>
<li>初始化重试规则和客户端一元/流式拦截器</li>
<li>初始化协议栈上的基础信息</li>
<li>相关 context 的超时控制</li>
<li>初始化并解析地址信息</li>
<li>创建与服务端之间的连接</li>
</ul>
<h4 id="连没连">连没连</h4>
<p>之前听到有的人说调用 <code>grpc.Dial</code> 后客户端就已经与服务端建立起了连接，但这对不对呢？我们先鸟瞰全貌，看看正在跑的 goroutine。如下：</p>
<p><img src="https://image.eddycjy.com/cf5793938b321b67b3b667655b375703.jpg" alt="image"></p>
<p>我们可以有几个核心方法一直在等待/处理信号，通过分析底层源码可得知。涉及如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ac</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>) <span style="color:#a6e22e">connect</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ac</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>) <span style="color:#a6e22e">resetTransport</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ac</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>) <span style="color:#a6e22e">createTransport</span>(<span style="color:#a6e22e">addr</span> <span style="color:#a6e22e">resolver</span>.<span style="color:#a6e22e">Address</span>, <span style="color:#a6e22e">copts</span> <span style="color:#a6e22e">transport</span>.<span style="color:#a6e22e">ConnectOptions</span>, <span style="color:#a6e22e">connectDeadline</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ac</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>) <span style="color:#a6e22e">getReadyTransport</span>()
</span></span></code></pre></div><p>在这里主要分析 goroutine 提示的 <code>resetTransport</code> 方法，看看都做了啥。核心代码如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ac</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">addrConn</span>) <span style="color:#a6e22e">resetTransport</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; ; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Shutdown</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">connectDeadline</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">dialDuration</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">updateConnectivityState</span>(<span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Connecting</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">newTr</span>, <span style="color:#a6e22e">addr</span>, <span style="color:#a6e22e">reconnect</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">tryAllAddrs</span>(<span style="color:#a6e22e">addrs</span>, <span style="color:#a6e22e">connectDeadline</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Shutdown</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">updateConnectivityState</span>(<span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">TransientFailure</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">timer</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">NewTimer</span>(<span style="color:#a6e22e">backoffFor</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">timer</span>.<span style="color:#a6e22e">C</span>:
</span></span><span style="display:flex;"><span>				<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Shutdown</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">newTr</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">healthcheckManagingState</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">updateConnectivityState</span>(<span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Ready</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">state</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Shutdown</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">updateConnectivityState</span>(<span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">TransientFailure</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>在该方法中会不断地去尝试创建连接，若成功则结束。否则不断地根据 <code>Backoff</code> 算法的重试机制去尝试创建连接，直到成功为止。从结论上来讲，单纯调用 <code>DialContext</code> 是异步建立连接的，也就是并不是马上生效，处于 <code>Connecting</code> 状态，而正式下要到达 <code>Ready</code> 状态才可用。</p>
<h4 id="真的连了吗">真的连了吗</h4>
<p><img src="https://image.eddycjy.com/eb935669c45405844c35aafbd5fe43d7.jpg" alt="image"></p>
<p>在抓包工具上提示一个包都没有，那么这算真正连接了吗？我认为这是一个表述问题，我们应该尽可能的严谨。如果你真的想通过 <code>DialContext</code> 方法就打通与服务端的连接，则需要调用 <code>WithBlock</code> 方法，虽然会导致阻塞等待，但最终连接会到达 <code>Ready</code> 状态（握手成功）。如下图：</p>
<p><img src="https://image.eddycjy.com/e0e28452229af52e70f87dd03c3a30c2.jpg" alt="image"></p>
<h3 id="二实例化-service-api">二、实例化 Service API</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SearchServiceClient</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">in</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">SearchRequest</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">CallOption</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">SearchResponse</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">searchServiceClient</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ClientConn</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewSearchServiceClient</span>(<span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ClientConn</span>) <span style="color:#a6e22e">SearchServiceClient</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">searchServiceClient</span>{<span style="color:#a6e22e">cc</span>}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>这块就是实例 Service API interface，比较简单。</p>
<h3 id="三调用">三、调用</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// search.pb.go
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">searchServiceClient</span>) <span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">in</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">SearchRequest</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">CallOption</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">SearchResponse</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">out</span> <span style="color:#f92672">:=</span> new(<span style="color:#a6e22e">SearchResponse</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">Invoke</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">&#34;/proto.SearchService/Search&#34;</span>, <span style="color:#a6e22e">in</span>, <span style="color:#a6e22e">out</span>, <span style="color:#a6e22e">opts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">out</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>proto 生成的 RPC 方法更像是一个包装盒，把需要的东西放进去，而实际上调用的还是 <code>grpc.invoke</code> 方法。如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">invoke</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">method</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">reply</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">CallOption</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cs</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">newClientStream</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">unaryStreamDesc</span>, <span style="color:#a6e22e">cc</span>, <span style="color:#a6e22e">method</span>, <span style="color:#a6e22e">opts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cs</span>.<span style="color:#a6e22e">SendMsg</span>(<span style="color:#a6e22e">req</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">cs</span>.<span style="color:#a6e22e">RecvMsg</span>(<span style="color:#a6e22e">reply</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>通过概览，可以关注到三块调用。如下：</p>
<ul>
<li>newClientStream：获取传输层 Trasport 并组合封装到 ClientStream 中返回，在这块会涉及负载均衡、超时控制、 Encoding、 Stream 的动作，与服务端基本一致的行为。</li>
<li>cs.SendMsg：发送 RPC 请求出去，但其并不承担等待响应的功能。</li>
<li>cs.RecvMsg：阻塞等待接受到的 RPC 方法响应结果。</li>
</ul>
<h4 id="连接">连接</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// clientconn.go
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>) <span style="color:#a6e22e">getTransport</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">failfast</span> <span style="color:#66d9ef">bool</span>, <span style="color:#a6e22e">method</span> <span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">transport</span>.<span style="color:#a6e22e">ClientTransport</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">balancer</span>.<span style="color:#a6e22e">DoneInfo</span>), <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">done</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">blockingpicker</span>.<span style="color:#a6e22e">pick</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">failfast</span>, <span style="color:#a6e22e">balancer</span>.<span style="color:#a6e22e">PickOptions</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">FullMethodName</span>: <span style="color:#a6e22e">method</span>,
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">toRPCErr</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">done</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>在 <code>newClientStream</code> 方法中，我们通过 <code>getTransport</code> 方法获取了 Transport 层中抽象出来的 ClientTransport 和 ServerTransport，实际上就是获取一个连接给后续 RPC 调用传输使用。</p>
<h3 id="四关闭连接">四、关闭连接</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// conn.Close()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>) <span style="color:#a6e22e">Close</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">csMgr</span>.<span style="color:#a6e22e">updateState</span>(<span style="color:#a6e22e">connectivity</span>.<span style="color:#a6e22e">Shutdown</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">blockingpicker</span>.close()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">rWrapper</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rWrapper</span>.close()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">bWrapper</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bWrapper</span>.close()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">ac</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">conns</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ac</span>.<span style="color:#a6e22e">tearDown</span>(<span style="color:#a6e22e">ErrClientConnClosing</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">channelz</span>.<span style="color:#a6e22e">IsOn</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">channelz</span>.<span style="color:#a6e22e">AddTraceEvent</span>(<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">channelzID</span>, <span style="color:#a6e22e">ted</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">channelz</span>.<span style="color:#a6e22e">RemoveEntry</span>(<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">channelzID</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>该方法会取消 ClientConn 上下文，同时关闭所有底层传输。涉及如下：</p>
<ul>
<li>Context Cancel</li>
<li>清空并关闭客户端连接</li>
<li>清空并关闭解析器连接</li>
<li>清空并关闭负载均衡连接</li>
<li>添加跟踪引用</li>
<li>移除当前通道信息</li>
</ul>
<h2 id="qa">Q&amp;A</h2>
<h3 id="1-grpc-metadata-是通过什么传输">1. gRPC Metadata 是通过什么传输？</h3>
<p><img src="https://image.eddycjy.com/129e458698c4745a32d44582161b51d8.jpg" alt="image"></p>
<h3 id="2-调用-grpcdial-会真正的去连接服务端吗">2. 调用 grpc.Dial 会真正的去连接服务端吗？</h3>
<p>会，但是是异步连接的，连接状态为正在连接。但如果你设置了 <code>grpc.WithBlock</code> 选项，就会阻塞等待（等待握手成功）。另外你需要注意，当未设置 <code>grpc.WithBlock</code> 时，ctx 超时控制对其无任何效果。</p>
<h3 id="3-调用-clientconn-不-close-会导致泄露吗">3. 调用 ClientConn 不 Close 会导致泄露吗？</h3>
<p>会，除非你的客户端不是常驻进程，那么在应用结束时会被动地回收资源。但如果是常驻进程，你又真的忘记执行 <code>Close</code> 语句，会造成的泄露。如下图：</p>
<p><strong>3.1. 客户端</strong></p>
<p><img src="https://image.eddycjy.com/e25418821200a0f7c8f9f81b22d21691.jpg" alt="image"></p>
<p><strong>3.2. 服务端</strong></p>
<p><img src="https://image.eddycjy.com/19ee203f0229aae4b91567bff25442e5.png" alt="image"></p>
<p><strong>3.3. TCP</strong></p>
<p><img src="https://image.eddycjy.com/f0d0b070be593820651230120b0374be.jpg" alt="image"></p>
<h3 id="4-不控制超时调用的话会出现什么问题">4. 不控制超时调用的话，会出现什么问题？</h3>
<p>短时间内不会出现问题，但是会不断积蓄泄露，积蓄到最后当然就是服务无法提供响应了。如下图：</p>
<p><img src="https://image.eddycjy.com/853b031a43495200d111d6f5239398a3.jpg" alt="image"></p>
<h3 id="5-为什么默认的拦截器不可以传多个">5. 为什么默认的拦截器不可以传多个？</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">chainUnaryClientInterceptors</span>(<span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">interceptors</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">dopts</span>.<span style="color:#a6e22e">chainUnaryInts</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">dopts</span>.<span style="color:#a6e22e">unaryInt</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">interceptors</span> = append([]<span style="color:#a6e22e">UnaryClientInterceptor</span>{<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">dopts</span>.<span style="color:#a6e22e">unaryInt</span>}, <span style="color:#a6e22e">interceptors</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">chainedInt</span> <span style="color:#a6e22e">UnaryClientInterceptor</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">interceptors</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">chainedInt</span> = <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	} <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">interceptors</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">chainedInt</span> = <span style="color:#a6e22e">interceptors</span>[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>	} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">chainedInt</span> = <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">method</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">reply</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">invoker</span> <span style="color:#a6e22e">UnaryInvoker</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">CallOption</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">interceptors</span>[<span style="color:#ae81ff">0</span>](<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">method</span>, <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">reply</span>, <span style="color:#a6e22e">cc</span>, <span style="color:#a6e22e">getChainUnaryInvoker</span>(<span style="color:#a6e22e">interceptors</span>, <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">invoker</span>), <span style="color:#a6e22e">opts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cc</span>.<span style="color:#a6e22e">dopts</span>.<span style="color:#a6e22e">unaryInt</span> = <span style="color:#a6e22e">chainedInt</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>当存在多个拦截器时，取的就是第一个拦截器。因此结论是允许传多个，但并没有用。</p>
<h3 id="6-真的需要用到多个拦截器的话怎么办">6. 真的需要用到多个拦截器的话，怎么办？</h3>
<p>可以使用 <a href="https://github.com/grpc-ecosystem/go-grpc-middleware">go-grpc-middleware</a> 提供的 <code>grpc.UnaryInterceptor</code> 和 <code>grpc.StreamInterceptor</code> 链式方法，方便快捷省心。</p>
<p>单单会用还不行，我们再深剖一下，看看它是怎么实现的。核心代码如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ChainUnaryClient</span>(<span style="color:#a6e22e">interceptors</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryClientInterceptor</span>) <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryClientInterceptor</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">interceptors</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">lastI</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">method</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">reply</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">cc</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">invoker</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryInvoker</span>, <span style="color:#a6e22e">opts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">CallOption</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">chainHandler</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">UnaryInvoker</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">curI</span>         <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>			)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">chainHandler</span> = <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">currentCtx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">currentMethod</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">currentReq</span>, <span style="color:#a6e22e">currentRepl</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">currentConn</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">currentOpts</span> <span style="color:#f92672">...</span><span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">CallOption</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">curI</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">lastI</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">invoker</span>(<span style="color:#a6e22e">currentCtx</span>, <span style="color:#a6e22e">currentMethod</span>, <span style="color:#a6e22e">currentReq</span>, <span style="color:#a6e22e">currentRepl</span>, <span style="color:#a6e22e">currentConn</span>, <span style="color:#a6e22e">currentOpts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">curI</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">interceptors</span>[<span style="color:#a6e22e">curI</span>](<span style="color:#a6e22e">currentCtx</span>, <span style="color:#a6e22e">currentMethod</span>, <span style="color:#a6e22e">currentReq</span>, <span style="color:#a6e22e">currentRepl</span>, <span style="color:#a6e22e">currentConn</span>, <span style="color:#a6e22e">chainHandler</span>, <span style="color:#a6e22e">currentOpts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">curI</span><span style="color:#f92672">--</span>
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">interceptors</span>[<span style="color:#ae81ff">0</span>](<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">method</span>, <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">reply</span>, <span style="color:#a6e22e">cc</span>, <span style="color:#a6e22e">chainHandler</span>, <span style="color:#a6e22e">opts</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>当拦截器数量大于 1 时，从 <code>interceptors[1]</code> 开始递归，每一个递归的拦截器 <code>interceptors[i]</code> 会不断地执行，最后才真正的去执行 <code>handler</code> 方法。同时也经常有人会问拦截器的执行顺序是什么，通过这段代码你得出结论了吗？</p>
<h3 id="7-频繁创建-clientconn-有什么问题">7. 频繁创建 ClientConn 有什么问题？</h3>
<p>这个问题我们可以反向验证一下，假设不公用 ClientConn 看看会怎么样？如下:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkSearch</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">GetClientConn</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;GetClientConn err: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">Search</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">conn</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;Search err: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>输出结果：</p>
<pre tabindex="0"><code>    ... connection error: desc = &#34;transport: Error while dialing dial tcp :10001: socket: too many open files&#34;
    ... connection error: desc = &#34;transport: Error while dialing dial tcp :10001: socket: too many open files&#34;
    ... connection error: desc = &#34;transport: Error while dialing dial tcp :10001: socket: too many open files&#34;
    ... connection error: desc = &#34;transport: Error while dialing dial tcp :10001: socket: too many open files&#34;
FAIL
exit status 1
</code></pre><p>当你的应用场景是存在高频次同时生成/调用 ClientConn 时，可能会导致系统的文件句柄占用过多。这种情况下你可以变更应用程序生成/调用 ClientConn 的模式，又或是池化它，这块可以参考 <a href="github.com/processout/grpc-go-pool">grpc-go-pool</a> 项目。</p>
<h3 id="8-客户端请求失败后会默认重试吗">8. 客户端请求失败后会默认重试吗？</h3>
<p>会不断地进行重试，直到上下文取消。而重试时间方面采用 backoff 算法作为的重连机制，默认的最大重试时间间隔是 120s。</p>
<h3 id="9-为什么要用-http2-作为传输协议">9. 为什么要用 HTTP/2 作为传输协议？</h3>
<p>许多客户端要通过 HTTP 代理来访问网络，gRPC 全部用 HTTP/2 实现，等到代理开始支持 HTTP/2 就能透明转发 gRPC 的数据。不光如此，负责负载均衡、访问控制等等的反向代理都能无缝兼容 gRPC，比起自己设计 wire protocol 的 Thrift，这样做科学不少。@ctiller @滕亦飞</p>
<h3 id="10-在-kubernetes-中-grpc-负载均衡有问题">10. 在 Kubernetes 中 gRPC 负载均衡有问题？</h3>
<p>gRPC 的 RPC 协议是基于 HTTP/2 标准实现的，HTTP/2 的一大特性就是不需要像 HTTP/1.1 一样，每次发出请求都要重新建立一个新连接，而是会复用原有的连接。</p>
<p>所以这将导致 kube-proxy 只有在连接建立时才会做负载均衡，而在这之后的每一次 RPC 请求都会利用原本的连接，那么实际上后续的每一次的 RPC 请求都跑到了同一个地方。</p>
<p>注：使用 k8s service 做负载均衡的情况下</p>
<h2 id="总结">总结</h2>
<ul>
<li>gRPC 基于 HTTP/2 + Protobuf。</li>
<li>gRPC 有四种调用方式，分别是一元、服务端/客户端流式、双向流式。</li>
<li>gRPC 的附加信息都会体现在 HEADERS 帧，数据在 DATA 帧上。</li>
<li>Client 请求若使用 grpc.Dial 默认是异步建立连接，当时状态为 Connecting。</li>
<li>Client 请求若需要同步则调用 WithBlock()，完成状态为 Ready。</li>
<li>Server 监听是循环等待连接，若没有则休眠，最大休眠时间 1s；若接收到新请求则起一个新的 goroutine 去处理。</li>
<li>grpc.ClientConn 不关闭连接，会导致 goroutine 和 Memory 等泄露。</li>
<li>任何内/外调用如果不加超时控制，会出现泄漏和客户端不断重试。</li>
<li>特定场景下，如果不对 grpc.ClientConn 加以调控，会影响调用。</li>
<li>拦截器如果不用 go-grpc-middleware 链式处理，会覆盖。</li>
<li>在选择 gRPC 的负载均衡模式时，需要谨慎。</li>
</ul>
<h2 id="参考">参考</h2>
<ul>
<li><a href="http://doc.oschina.net/grpc">http://doc.oschina.net/grpc</a></li>
<li><a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md">https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md</a></li>
<li><a href="https://juejin.im/post/5b88a4f56fb9a01a0b31a67e">https://juejin.im/post/5b88a4f56fb9a01a0b31a67e</a></li>
<li><a href="https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html">https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html</a></li>
<li><a href="https://github.com/grpc/grpc-go/issues/1953">https://github.com/grpc/grpc-go/issues/1953</a></li>
<li><a href="https://www.zhihu.com/question/52670041">https://www.zhihu.com/question/52670041</a></li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/grpc-gateway/2019-06-22-grpc-gateway-tls/">「连载四」gRPC&#43;gRPC Gateway 能不能不用证书？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/talk/2019-06-16-defer-loss/">Go1.12 defer 会有性能损耗，尽量不要用？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/defer/2019-05-27-defer/">深入理解 Go defer</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/panic/2019-05-21-panic-and-recover/">深入理解 Go panic and recover</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/talk/2019-05-20-stack-heap/">我要在栈上。不，你应该在堆上</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.asia/posts/posts/go/grpc-gateway/2019-06-22-grpc-gateway-tls/"
                                                    rel="prev">「连载四」gRPC&#43;gRPC Gateway 能不能不用证书？</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.asia/posts/posts/go/tools/2019-07-12-go-tool-trace/">Go 大杀器之跟踪剖析 trace</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.asia/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    <section id="qrcode" class="widget widget_media_image">
        <h2 class="widget-title">微信公众号</h2>
        <img width="258" height="258"
            src="https://www.lican.asia/wechat/lican.png"
            class="image wp-image-5514  attachment-full size-full" alt=""
            style="max-width: 100%; height: auto;"
            sizes="(max-width: 258px) 100vw, 258px">
    </section>
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.asia All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>